package com.hex.ds.hdrs.engine.service;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.hex.ds.hdrs.app.service.AppManager;
import com.hex.ds.hdrs.calendar.service.WorkCalendarService;
import com.hex.ds.hdrs.center.service.CenterManager;
import com.hex.ds.hdrs.common.constant.Const;
import com.hex.ds.hdrs.common.utils.BaseUtil;
import com.hex.ds.hdrs.common.utils.HdrsDateUtil;
import com.hex.ds.hdrs.period.job.po.PeriodJob;
import com.hex.ds.hdrs.period.job.po.PeriodJobDef;
import com.hex.ds.hdrs.period.job.service.PeriodJobDefService;
import com.hex.ds.hdrs.period.job.service.PeriodJobService;
import com.hex.ds.hdrs.period.task.service.PeriodTaskDefService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * @Package: com.hex.ds.hdrs.engine.service
 * @ClassName GenerateService
 * @Description:
 * @Author: 生成实例定时器<gc.zuo>
 * @Date 2021/7/8 16:06
 * @Version v1.0
 **/
@Slf4j
@Service
@EnableScheduling
@Lazy(false)
public class GenerateService {

    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
    private static final SimpleDateFormat eightDateFormat = new SimpleDateFormat("yyyyMMdd");
    private static final SimpleDateFormat hourFormat = new SimpleDateFormat("HH");

    @Autowired
    private PeriodJobDefService periodJobDefService;
    @Autowired
    private PeriodJobService periodJobService;
    @Autowired
    private PeriodTaskDefService periodTaskDefService;
    @Autowired
    private WorkCalendarService workCalendarService;
    @Autowired
    private CenterManager centerManager;
    @Autowired
    private AppManager appManager;

    private final AtomicBoolean isTaskGenerating = new AtomicBoolean(false);

    /**
     * 定时生成任务实例
     */
    @Scheduled(fixedDelayString = "${scheduled.task.fixedDelayString}", initialDelayString = "${scheduled.task.initialDelayString}")
    public void timedTaskGenerator() {
        tryTaskGenerator();
    }

    /**
     * 手动生成任务实例
     *
     * @author Yin.Yang
     * @date 2024/5/22 18:40
     */
    public void generateTaskInstance() {
        tryTaskGenerator();
    }

    /**
     * 尝试生成任务实例
     * CAS尝试将标志位从false设置为true以确保当前没有其他任务生成正在进行。
     * 如果成功获取锁，则调用taskGenerator()执行任务生成逻辑。
     * 在任务生成完成后，重置标志位为false以允许后续任务生成。
     * 如果标志位为true，表示已有任务生成进行中，则跳过本次任务生成。
     * 避免在定时任务生成实例时，手动调用造成重复生成任务实例的问题。
     *
     * @author Yin.Yang
     * @date 2024/5/27 10:34
     */
    private void tryTaskGenerator() {
        if (isTaskGenerating.compareAndSet(false, true)) {
            try {
                taskGenerator();
            } finally {
                isTaskGenerating.set(false);
            }
        } else {
            log.info("任务实例生成正在进行，跳过本次生成。");
        }
    }

    /**
     * 任务实例生成器
     * 首先检查当前节点是否具有执行权限（例如，节点是否为master），然后获取当前日期，
     * 并查询出所有未生成当日任务的作业定义。
     * 对于每个作业定义，它检查是否满足生成条件，如应用是否启动、作业调度频率（如日、周、月等）
     * 以及特定的时间偏移处理。如果所有条件满足，它将计算业务日期，并检查是否为工作日，
     * 再决定是否生成新的任务实例。
     * 如果生成条件满足，这些作业定义将被添加到 周期作业实例表(PERIOD_JOB) 中。
     * insertPeriodJobList方法批量将这些作业定义插入到作业实例表中，为每个作业实例生成相应的任务。
     *
     * @date 2024/5/27 10:34
     */
    public void taskGenerator() {
        log.debug("【实例生成器】开始");
        if (!centerManager.isCanDeal()) {
            log.warn("【生成器不允许执行】该中心非master!");
            return;
        }
        String currDate = HdrsDateUtil.getNormalYYYYMMDDDate(new Date());
        List<PeriodJobDef> periodJobTasksList = new ArrayList<PeriodJobDef>();
        boolean ok = false;
        //遍历获取未产生当日Job的JobDef集合
        List<PeriodJobDef> notGeneratedJobList = periodJobDefService.getJobDefList(currDate);
        log.info("【实例生成器】当天未生成实例的作业定义,数据大小: {}", (notGeneratedJobList == null ? 0 : notGeneratedJobList.size()));
        try {
            for (PeriodJobDef jobDef : notGeneratedJobList) {
                if (StrUtil.isEmpty(jobDef.getAppCode())) {
                    log.error("【实例生成器】作业编码【{}】appCode为空,跳过该作业",jobDef.getJobCode());
                    continue;
                }
                if (!appManager.isValidAppSession(jobDef.getAppCode())) {
                    log.debug("【该作业不允许生成实例】所属应用未启动!");
                    continue;
                }
                //调度频率
                String jobPeriod = jobDef.getJobPeriod();
                //日期表达式
                String dataExpression = jobDef.getDateExpression() == null ? "" : jobDef.getDateExpression();
                try {
                    if ("D".equals(jobPeriod)) {
                        ok = true;
                    } else if ("W".equals(jobPeriod)) {
                        ok = isWeekOk(dataExpression, currDate);
                    } else if ("M".equals(jobPeriod)) {
                        ok = isMonthOk(dataExpression, currDate);
                    } else if ("Q".equals(jobPeriod)) {
                        ok = isQuarterOk(dataExpression, currDate);
                    } else if ("X".equals(jobPeriod)) {
                        //ok = isYearOk(dataExpression, currDate);
                    } else if ("Y".equals(jobPeriod)) {
                        ok = isYearOk(dataExpression, currDate);
                    } else {
                        ok = isOtherOk(dataExpression, currDate);
                    }
                } catch (Exception e) {
                    log.error("jobCode: {} jobStDate: {} errorInfo: {} ", jobDef.getJobCode(), jobDef.getJobStDate(), e.toString());
                    ok = false;
                }
                if (ok) {
                    // 判断时间是否达到要求
                    // 当前小时
                    Calendar calendar = Calendar.getInstance();
                    int currentHour = Integer.valueOf(hourFormat.format(calendar.getTime()));
                    int hour = 0;
                    // 偏移小时 作用：当<=24时，生成当天实例  >24时，则生成前n的实例
                    int offSetHour = jobDef.getOffsetHour();
                    if (offSetHour < 0) {
                        offSetHour = 0;
                    } else if (offSetHour > 0) {
                        hour = offSetHour % 24;
                    }
                    // offSetHour / 24 向前推n天
                    calendar.add(Calendar.DATE, -1 * (offSetHour / 24));
                    if (currentHour >= hour) {
                        String bizDate = "";
                        // 2021-05-25 判断是否单次任务
                        if (Const.IS_SINGLE_JOB.equals(jobDef.getIsSingleJob())) {
                            // 2021-05-25 若是单次任务，业务日期取自日期表达式
                            bizDate = jobDef.getDateExpression();
                        } else {
                            bizDate = eightDateFormat.format(calendar.getTime());
                        }

                        //判断业务日期是否是工作日
                        Boolean bool = (Const.CYCLE_JOB_IS_WORD_DAY_RUN_NO.equals(jobDef.getIsWorkRun())) ? true : false;
                        if (Const.CYCLE_JOB_IS_WORD_DAY_RUN_YES.equals(jobDef.getIsWorkRun())) {
                            try {
                                bool = this.workCalendarService.checkDateStyle(bizDate);
                                bool = this.workCalendarService.checkWorkCalendar(bizDate,bool);
                            } catch (Exception e) {
                                log.error("判断日期是否是工作日异常: {}", e.getMessage());
                            }
                        }
                        if (bool) {
                            if (offSetHour >= 24) {
                                // 根据作业编码和业务日期查询该作业实例是否存在
                                // 当偏移小时大于24时将生成前n天的实例，此处过滤掉已生成过实例的数据
                                if (periodJobService.queryByJobCodeAndBizDate(jobDef.getJobCode(), bizDate) == null) {
                                    jobDef.setBizDate(bizDate);
                                    periodJobTasksList.add(jobDef);
                                }
                            } else {
                                jobDef.setBizDate(bizDate);
                                periodJobTasksList.add(jobDef);
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
            log.error("生成实例错误, 异常:{}", e.getMessage(),e);
        }
        //将符合条件的作业定义表数据插入到实例表中
        if (periodJobTasksList.size() > 0) {
            insertPeriodJobList(periodJobTasksList);
        }
        log.debug("【实例生成器】结束");
    }

    /**
     * @Method: isOffsetCorrect
     * @Param: [jobDef]
     * @Return: java.lang.Boolean
     * @Description: 判断偏移量是否小于等于当前时间和作业创建时间的时差
     * @Author: ji.tang
     * @Date: 2022/1/26 15:35
     * @Version: V1.0
     */
    private Boolean isOffsetCorrect(PeriodJobDef jobDef) {
        if (StringUtils.isEmpty(jobDef.getCrtTs())) {
            return false;
        }
        String[] crtTss = jobDef.getCrtTs().split(" ");
        String beginTime = crtTss[0] + " 00:00:00";
        long[] time = HdrsDateUtil.getDistanceTimes(beginTime, HdrsDateUtil.getCurTs());
        if (time != null && time.length > 0) {
            long hour = time[1];
            return hour >= jobDef.getOffsetHour();
        }
        return false;
    }


    private boolean isOtherOk(String expression, String currDate) {
        boolean ok = false;
        boolean yearOk = false;
        boolean monthOk = false;
        boolean dayOk = false;
        if (expression != null && !"".equals(expression)) {
            String[] args = expression.split(" ");
            if (args.length == 3) {
                int currYear = Integer.valueOf(currDate.substring(0, 4));
                int currMonth = Integer.valueOf(currDate.substring(4, 6));
                int currDay = Integer.valueOf(currDate.substring(6, 8));
                String years = args[0];
                String months = args[1];
                String days = args[2];
                // 判断年
                if ("*".equals(years)) {
                    yearOk = true;
                } else if (years.contains(",")) {
                    yearOk = isMultiOk(years, currYear);
                } else if (years.contains("-")) {
                    yearOk = isRegionOk(years, currYear);
                } else {
                    try {
                        if (Integer.valueOf(years) == currYear) {
                            yearOk = true;
                        }
                    } catch (NumberFormatException e) {
                        throw new RuntimeException("错误的表达式：" + expression);
                    }
                }
                // 判断月
                if ("*".equals(months)) {
                    monthOk = true;
                } else if (months.contains(",")) {
                    monthOk = isMultiOk(months, currMonth);
                } else if (months.contains("-")) {
                    monthOk = isRegionOk(months, currMonth);
                } else {
                    try {
                        if (Integer.valueOf(months) == currMonth) {
                            monthOk = true;
                        }
                    } catch (NumberFormatException e) {
                        throw new RuntimeException("错误的表达式：" + expression);
                    }
                }
                // 判断日
                if ("*".equals(days)) {
                    dayOk = true;
                } else if (days.contains(",")) {
                    dayOk = isMultiOk(days, currDay);
                } else if (days.contains("-")) {
                    dayOk = isRegionOk(days, currDay);
                } else {
                    try {
                        if (Integer.valueOf(days) == currDay) {
                            dayOk = true;
                        }
                    } catch (NumberFormatException e) {
                        throw new RuntimeException("错误的表达式：" + expression);
                    }
                }
                // 年、月、日判断
                if (yearOk && monthOk && dayOk) {
                    ok = true;
                }
            }
        }
        return ok;
    }

    private boolean isYearOk(String expression, String currDate) {
        int currYear = Integer.valueOf(currDate.substring(0, 4));
        int days = HdrsDateUtil.getDays(currYear);
        int offset = 1;
        if ("".equals(expression)) {
            offset = 1;
        } else {
            try {
                offset = Integer.valueOf(expression);
                if (offset <= -366 || offset >= 366 || offset == 0) {
                    return false;
                } else if (offset < 0) {
                    offset = days + offset;
                }
            } catch (NumberFormatException e) {
                throw new RuntimeException("错误的表达式：" + expression);
            }
        }
        int day = HdrsDateUtil.getDayOfYear(currDate);
        log.debug("年频率===>表达式：{} 当天日期：{} 一年中第：{}天", expression, currDate, day);
        if (offset == day) {
            return true;
        }
        return false;
    }

    private boolean isQuarterOk(String expression, String currDate) {
        String currYear = currDate.substring(0, 4);
        int offset = 0;
        if ("".equals(expression)) {
            offset = 0;
        } else {
            try {
                offset = Integer.valueOf(expression);
                if (offset <= -31 || offset >= 31) {
                    return false;
                } else if (offset > 0) {
                    offset = offset - 1;
                }
            } catch (NumberFormatException e) {
                throw new RuntimeException("错误的表达式：" + expression);
            }
        }
        Calendar calendar = Calendar.getInstance();
        int[] is = new int[]{1, 4, 7, 10};
        for (int i : is) {
            if (offset < 0) {
                i = i + 2;
            }
            String month = "" + i;
            if (i < 10) {
                month = "0" + i;
            }
            Date date = eightDateFormat.parse(currYear + month + "01", new ParsePosition(0));
            calendar.setTime(date);
            if (offset < 0) {
                calendar.add(Calendar.MONTH, 1);
            }
            calendar.add(Calendar.DATE, offset);
            String dateStr = eightDateFormat.format(calendar.getTime());
            log.debug("结果日期：{} 当天日期：{} ", dateStr, currDate);
            if (dateStr.equals(currDate)) {
                return true;
            }
        }
        return false;
    }

    private boolean isMonthOk(String expression, String currDate) {
        String currYear = currDate.substring(0, 4);
        int offset = 0;
        if ("".equals(expression)) {
            offset = 0;
        } else {
            try {
                offset = Integer.valueOf(expression);
                if (offset <= -31 || offset >= 31) {
                    return false;
                } else if (offset > 0) {
                    offset = offset - 1;
                }
            } catch (NumberFormatException e) {
                throw new RuntimeException("错误的表达式：" + expression);
            }
        }
        Calendar calendar = Calendar.getInstance();
        for (int i = 1; i <= 12; i++) {
            String month = "" + i;
            if (i < 10) {
                month = "0" + i;
            }
            Date date = eightDateFormat.parse(currYear + month + "01", new ParsePosition(0));
            calendar.setTime(date);
            if (offset < 0) {
                calendar.add(Calendar.MONTH, 1);
            }
            calendar.add(Calendar.DATE, offset);
            String dateStr = eightDateFormat.format(calendar.getTime());
            log.debug("结果日期：{} 当天日期：{}", dateStr, currDate);
            if (dateStr.equals(currDate)) {
                return true;
            }
        }
        return false;
    }

    private boolean isWeekOk(String expression, String currDate) {
        int offset = 1;
        if ("".equals(expression)) {
            offset = 1;
        } else {
            try {
                offset = Integer.valueOf(expression);
                if (offset < 0 || offset >= 7) {
                    return false;
                }
            } catch (NumberFormatException e) {
                throw new RuntimeException("错误的表达式：" + expression);
            }
        }
        int day = HdrsDateUtil.getDayOfWeek(currDate);
        log.debug("周频率===>表达式：{} 当天日期：{} 一周中的：{}", expression, currDate, HdrsDateUtil.getDayOfWeekCn(currDate));
        if (offset == day) {
            return true;
        }
        return false;
    }

    private boolean isRegionOk(String strs, int value) {
        String[] args = strs.split("-");
        if (args.length == 2) {
            int left = 0;
            try {
                left = Integer.valueOf(args[0]);
            } catch (NumberFormatException e) {

                return false;
            }
            int right = 0;
            try {
                right = Integer.valueOf(args[1]);
            } catch (NumberFormatException e) {

                return false;
            }
            if (left <= value && right >= value) {
                return true;
            }
        }
        return false;
    }

    private boolean isMultiOk(String str, int value) {
        String[] args = str.split(",");
        for (String arg : args) {
            if (arg.contains("-")) {
                String[] arr = arg.split("-");
                if (arr.length == 2) {
                    int left = 0;
                    try {
                        left = Integer.valueOf(arr[0]);
                    } catch (NumberFormatException e) {

                        return false;
                    }
                    int right = 0;
                    try {
                        right = Integer.valueOf(arr[1]);
                    } catch (NumberFormatException e) {

                        return false;
                    }
                    if (left <= value && right >= value) {
                        return true;
                    }
                }
            } else {
                int val = 0;
                try {
                    val = Integer.valueOf(arg);
                } catch (NumberFormatException e) {

                    return false;
                }
                if (val == value) {
                    return true;
                }
            }
        }
        return false;
    }


    /**
     * @Method: insertPeriodJobList
     * @Param: [periodJobDefList]
     * @Return: void
     * @Description： 批量新增作业实例
     * @Author： jd.chen
     * @Date： 2021/6/17 17:15
     * @Version： V1.0
     */
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void insertPeriodJobList(List<PeriodJobDef> periodJobDefList) {
        int batchSize = 2000; // 定义每批次处理的大小
        int count = 0; // 初始化计数器
        log.info("【实例生成器】开始生成条件过滤后的作业定义,数据大小: {}", periodJobDefList.size());
        for (PeriodJobDef def : periodJobDefList) {
            try {
                log.info("生成作业实例，当前位置：{}，作业编码：{}，业务日期：{}，偏移小时：{}", periodJobDefList.indexOf(def),def.getJobCode(), def.getBizDate(), def.getOffsetHour());
                PeriodJob periodJob = clone(def);
                Boolean bool = periodJobService.add(periodJob);
                if (bool) {
                    periodTaskDefService.generateJobTask(periodJob);
                }
                count++; // 增加计数器
                // 如果达到一个批次的数量，则休眠5分钟
                if (count % batchSize == 0) {
                    log.info("已处理{}个作业实例，开始休眠5分钟", count);
                    Thread.sleep(300000); // 休眠5分钟
                }
            } catch (Exception e) {
                log.error("插入实例数据错误, 异常:{}", e.getMessage(), e);
                throw new RuntimeException(e);
            }
        }
        log.info("【实例生成器】生成条件过滤后的作业定义结束,数据大小: {}", count);
    }


    private PeriodJob clone(PeriodJobDef def) {
        PeriodJob job = new PeriodJob();
        job.setPkId(BaseUtil.getPkId());
        job.setAppCode(def.getAppCode());
        job.setAppointAgentCode(def.getAppointAgentCode());
        job.setJobCode(def.getJobCode());
        job.setJobName(def.getJobName());
        job.setJobStatus(Const.JOB_TO_DO);
        job.setJobEndTs(def.getJobEndDate());
        job.setSortNo(def.getSortNo());
        job.setBizDate(def.getBizDate());
        job.setCrtTs(HdrsDateUtil.getCurrentTime());
        job.setUpTs(HdrsDateUtil.getCurrentTime());
        job.setUpUser(def.getUpUser());
        job.setCrtUser(def.getCrtUser());
        return job;
    }

    /**
     * @Method: isFirstJobDef
     * @Param: [jobCode]
     * @Return: java.lang.Boolean
     * @Description: 判断作业实例是否存在
     * @Author: ji.tang
     * @Date: 2022/1/26 15:04
     * @Version: V1.0
     */
    private Boolean isFirstJobDef(String jobCode) {
        return CollectionUtil.isNotEmpty(periodJobService.queryListByJobCode(jobCode));
    }

}
